/**
 * 
 */
package network.internal.daemon;

import static commons.Preconditions.checkIsPositive;
import static commons.Preconditions.checkIsValidPort;
import static commons.Preconditions.checkNotNull;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ServerSocketFactory;

import network.Stoppable;
import network.communication.ConnectionFactory;
import network.communication.MessageHandler;
import network.daemon.Daemon;
import network.internal.thread.ServerThreadFactory;

import commons.exception.AlreadyRunningException;
import commons.exception.ClosedConnectionException;
import commons.exception.NotRunningException;

/**
 * @author armstrong
 * 
 */
public class ConnectionHandlerDaemon implements Daemon {

	private class ConnectionHandler implements Runnable, Stoppable {

		private final ServerSocket	serverSocket;
		private boolean				continueRunning;

		public ConnectionHandler(final ServerSocket socket) {
			this.serverSocket = socket;
			continueRunning = true;
		}

		@Override
		public void run() {
			Socket clientSocket = null;
			while (continueRunning) {
				try {
					clientSocket = serverSocket.accept();
					scheduler.submit(new HandleCaller(clientSocket,
							serverHandler));
				} catch (final IOException e) {
					e.printStackTrace();
				}
			}
		}

		@Override
		public void stop() throws IOException {
			continueRunning = false;
			serverSocket.close();
		}
	}

	private class HandleCaller implements Runnable {

		private final Socket			clientSocket;
		private final MessageHandler	messageHandler;

		public HandleCaller(final Socket clientSocket,
							final MessageHandler messageHandler) {

			checkNotNull("clientSocket cannot be null.", clientSocket);
			checkNotNull("messageHandler cannot be null.", messageHandler);

			this.clientSocket = clientSocket;
			this.messageHandler = messageHandler;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			try {
				messageHandler.handle(connectionFactory
						.createConnection(clientSocket));
			} catch (final IOException e) {
				e.printStackTrace();
			} catch (final ClosedConnectionException e) {
				e.printStackTrace();
			}
		}
	}

	private final ServerSocket		serverSocket;
	private final MessageHandler	serverHandler;
	private final ConnectionFactory	connectionFactory;
	private final ConnectionHandler	connectionHandler;

	private boolean					running;

	private final ExecutorService	scheduler;

	public ConnectionHandlerDaemon(final int port,
									final MessageHandler serverHandler,
									final ServerSocketFactory socketFactory,
									final ConnectionFactory connectionFactory,
									final int threadsPoolSize)
			throws IOException {

		checkIsValidPort("invalid port.", port);
		checkNotNull("serverHandler cannot be null.", serverHandler);
		checkNotNull("socketFactory cannot be null.", socketFactory);
		checkNotNull("connectionFactory cannot be null.", connectionFactory);
		checkIsPositive("threadsPoolSize must be positive.", threadsPoolSize);

		scheduler = Executors.newFixedThreadPool(threadsPoolSize,
				new ServerThreadFactory());
		serverSocket = socketFactory.createServerSocket(port);
		this.serverHandler = serverHandler;
		this.connectionHandler = new ConnectionHandler(serverSocket);
		running = false;
		this.connectionFactory = connectionFactory;
	}

	@Override
	public boolean isRunning() {
		return running;
	}

	@Override
	public void start() throws AlreadyRunningException {
		if (running) {
			throw new AlreadyRunningException("Daemon already running.", this);
		}
		running = true;
		new Thread(connectionHandler).start();
	}

	@Override
	public void stop() throws NotRunningException, IOException {
		if (!running) {
			throw new NotRunningException("Daemon is not running.", this);
		}
		running = false;
		connectionHandler.stop();
	}
}
